package apple.ui.awt;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.util.HashMap;
import java.util.Map;

import apple.core.Apple;
import apple.ui.IMonitor;

public class Monitor extends Component implements IMonitor {

   private static final long serialVersionUID = 1L;

   private BufferedImage image;

   private Map<Byte, Integer> colorMap;

   private Apple apple;

   public Monitor() {
      Dimension size = new Dimension(IMonitor.TOTAL_HORIZONTAL_SIZE,
            IMonitor.TOTAL_VERTICAL_SIZE);
      setPreferredSize(size);

      // these dimensions depend on the size of the image data
      image = new BufferedImage(280, 192, BufferedImage.TYPE_BYTE_INDEXED);
      colorMap = getColorMap(image);
   }

   public String toString() {
      return COLOR_MONITOR_SHORT_NAME;
   }

   public void setApple(Apple apple) {
      this.apple = apple;
   }

   /*
    * See page 23.
    * 
    * The samples inserted into the map depend on the color model which depends
    * on the image type. But since the images used will vary only in dimensions
    * and not in type, the color models should always be the same.
    */
   private static Map<Byte, Integer> getColorMap(BufferedImage image) {
      Map<Byte, Integer> colorMap = new HashMap<Byte, Integer>(0x10);
      IndexColorModel colorModel = (IndexColorModel) image.getColorModel();

      int index;
      index = getIndex(colorModel, Color.BLACK);
      colorMap.put(Byte.valueOf((byte) 0x0), index);
      index = getIndex(colorModel, Color.MAGENTA);
      colorMap.put(Byte.valueOf((byte) 0x1), index);
      index = getIndex(colorModel, Color.BLUE);
      colorMap.put(Byte.valueOf((byte) 0x2), index);
      index = getIndex(colorModel, Color.BLUE);
      colorMap.put(Byte.valueOf((byte) 0x3), index);
      index = getIndex(colorModel, Color.GREEN);
      colorMap.put(Byte.valueOf((byte) 0x4), index);
      index = getIndex(colorModel, Color.GRAY);
      colorMap.put(Byte.valueOf((byte) 0x5), index);
      index = getIndex(colorModel, Color.BLUE);
      colorMap.put(Byte.valueOf((byte) 0x6), index);
      index = getIndex(colorModel, Color.BLUE);
      colorMap.put(Byte.valueOf((byte) 0x7), index);
      index = getIndex(colorModel, Color.BLACK);
      colorMap.put(Byte.valueOf((byte) 0x8), index);
      index = getIndex(colorModel, Color.ORANGE);
      colorMap.put(Byte.valueOf((byte) 0x9), index);
      index = getIndex(colorModel, Color.GRAY);
      colorMap.put(Byte.valueOf((byte) 0xA), index);
      index = getIndex(colorModel, Color.PINK);
      colorMap.put(Byte.valueOf((byte) 0xB), index);
      index = getIndex(colorModel, Color.GREEN);
      colorMap.put(Byte.valueOf((byte) 0xC), index);
      index = getIndex(colorModel, Color.YELLOW);
      colorMap.put(Byte.valueOf((byte) 0xD), index);
      index = getIndex(colorModel, Color.BLUE);
      colorMap.put(Byte.valueOf((byte) 0xE), index);
      index = getIndex(colorModel, Color.WHITE);
      colorMap.put(Byte.valueOf((byte) 0xF), index);

      return colorMap;
   }

   private static int getIndex(IndexColorModel model, Color color) {
      int[] comps = new int[3];
      comps[0] = color.getRed();
      comps[1] = color.getGreen();
      comps[2] = color.getBlue();
      return model.getDataElement(comps, 0);
   }

   public void drawImage(int[][] imageData) {

      WritableRaster raster = image.getRaster();
      int height = raster.getHeight();
      int width = raster.getWidth();

      for (int y = 0; y < height; y++) {
         for (int x = 0; x < width; x++) {
            byte colorKey = (byte) imageData[y][x];
            int index = colorMap.get(colorKey);
            raster.setSample(x, y, 0, index);
         }
      }

      // why is this necessary if the image is not double-buffered?
      repaint(IMonitor.BORDER_HORIZONTAL_SIZE, IMonitor.BORDER_VERTICAL_SIZE,
            IMonitor.CONTENT_HORIZONTAL_SIZE, IMonitor.CONTENT_VERTICAL_SIZE);
   }

   public void paint(Graphics g) {
      Graphics2D g2d = (Graphics2D) g.create();
      Rectangle rect = g2d.getClipBounds();
      if (rect != null) {
         if (rect.x < IMonitor.BORDER_HORIZONTAL_SIZE
               || rect.y < IMonitor.BORDER_VERTICAL_SIZE
               || rect.x + rect.width > IMonitor.BORDER_HORIZONTAL_SIZE
                     + IMonitor.CONTENT_HORIZONTAL_SIZE
               || rect.y + rect.height > IMonitor.BORDER_VERTICAL_SIZE
                     + IMonitor.CONTENT_VERTICAL_SIZE) {
            g2d.setBackground(Color.BLACK);
            g2d.clearRect(0, 0, IMonitor.TOTAL_HORIZONTAL_SIZE,
                  IMonitor.TOTAL_VERTICAL_SIZE);
            System.out.format(
                  "Out of bounds: x=%d, y=%d, width=%d, height=%d%n", rect.x,
                  rect.y, rect.width, rect.height);
         }
      }
      g2d.drawImage(image, IMonitor.BORDER_HORIZONTAL_SIZE,
            IMonitor.BORDER_VERTICAL_SIZE, IMonitor.CONTENT_HORIZONTAL_SIZE,
            IMonitor.CONTENT_VERTICAL_SIZE, null);
      g2d.dispose();
   }
}
